TI_GDT  equ   0
RPL0    equ   0
SELECTOR_VIDEO  equ   (0x0003<<3) + TI_GDT + RPL0

section .data
put_int_buffer    dq    0     ; 定义8字节缓冲区用于数字到字符的转换

[bits 32]
section .text

; Function
; ---------------------------------
; put_str 
; ---------------------------------
; @brief: Print a null-terminated string.
; @params: The string to print is expected to be in the stack.
;          (String address is passed as a parameter in the stack)
; @return: None
global put_str
put_str:
  push ebx;
  push ecx;
  xor ecx, ecx;
  mov ebx, [esp + 12]; Get the address of the input string
.goon:
  mov cl, [ebx];
  cmp cl, 0
  jz .str_over
  push ecx;
  call put_char;
  add esp, 4;
  inc ebx;
  jmp .goon;
.str_over:
  pop ecx;
  pop ebx;
  ret;

; Function
; ---------------------------------
; put_char 
; ---------------------------------
; @brief: Display a character on the screen at the current cursor position.
;         Handles special characters such as carriage return, line feed, and backspace.
; @params: The character to be displayed is expected to be in the stack.
;          (Character address is passed as a parameter in the stack)
; @return: None
;-------------------------------------------
global put_char
put_char:
  pushad    ; 保存现场
  mov ax, SELECTOR_VIDEO
  mov gs, ax

  ; get cursor position
  ; high 8 bits
  mov dx, 0x3d4
  mov al, 0x0e
  out dx, al
  mov dx, 0x3d5
  in  al, dx
  mov ah, al

  ; low 8 bits
  mov dx, 0x3d4
  mov al, 0x0f
  out dx, al
  mov dx, 0x3d5
  in  al, dx

  ;save position to bx
  ;bx as the base address
  mov bx, ax

  ;get waiting-printed charactor
  mov ecx, [esp + 36]

  cmp cl, 0xd
  jz .is_carriage_return;CR 回车
  cmp cl, 0xa
  jz .is_line_feed      ;LF 换行
  cmp cl, 0x8
  jz .is_backspace
  jmp .put_other

;------------------------------------------
  ;若为 CR(\r), 移动光标到行首
  ;若为 LF(\n)，移动光标到下一行的同一列
.is_carriage_return:
  xor dx, dx
  mov ax, bx
  mov si, 80
  div si      ;dx = bx/80的余数
  sub bx, dx

  jmp .set_cursor
  
.is_line_feed:
  add bx, 80
  cmp bx, 2000
  
  jge .roll_screen   ;超出屏幕范围，滚屏，设置光标
  jmp .set_cursor

.is_backspace:
  dec bx
  shl bx, 1 ;光标位置乘2才是显存位置

  mov byte [gs:bx], 0x20  ; 空格
  inc bx
  mov byte [gs:bx], 0x07
  shr bx, 1
  jmp .set_cursor

.put_other:
  shl bx, 1
  mov [gs:bx], cl
  inc bx
  mov byte [gs:bx], 0x07
  shr bx, 1
  inc bx
  cmp bx,2000
  jge .roll_screen
  jmp .set_cursor

; -----------------------------------
.roll_screen:
  cld
  mov ecx, 960  ;(2000-80)*2/4=960
  mov esi, 0xc00b80a0   ;第一行行首
  mov edi, 0xc00b8000   ;第二行行首
  rep movsd
  
  ;set last line to empty
  mov ebx, 3840   ;(2000-80)*2=3840
  mov ecx, 80

  .cls:
    mov word [gs:ebx], 0x0720 ;黑底白字的空格
    add ebx, 2
    loop .cls
    mov bx, 1920    ;2000-80=1920

.set_cursor:
  ; high 8 bits
  mov dx, 0x03d4
  mov al, 0x0e
  out dx, al
  mov dx, 0x03d5
  mov al, bh
  out dx, al

  ; low 8 bits
  mov dx, 0x03d4
  mov al, 0x0f
  out dx, al
  mov dx, 0x03d5
  mov al, bl
  out dx, al

;-------- finished -----------
  popad
  ret

; Function
; ---------------------------------
; set_cursor 
; ---------------------------------
; @brief:
;   set the position of cursor on the screen
; @params:
;   The position is passed as a parameter.
;   (Position format: high 8 bits in bh, low 8 bits in bl)
; @return:
;   none
global set_cursor
set_cursor:
  ; save all register
  pushad
  ; Get the position parameter from the stack
  mov bx, [esp + 36]
  ; high 8 bits
  mov dx, 0x03d4
  mov al, 0x0e
  out dx, al
  mov dx, 0x03d5
  mov al, bh
  out dx, al

  ; low 8 bits
  mov dx, 0x03d4
  mov al, 0x0f
  out dx, al
  mov dx, 0x03d5
  mov al, bl
  out dx, al

;-------- finished -----------
  popad
  ret

; Function
; ---------------------------------
; put_int
; ---------------------------------
; @brief: 
;   Convert Little-Endian Integer to ASCII and Reverse
; @params: 
;   The integer to be printed is passed as a parameter on the stack.
; @return:
;   Prints the hexadecimal representation of the integer on the screen without the 0x prefix.
;   For example, if the input is 15 in decimal, it will print 'f', not '0xf'.
global put_int
put_int:
   pushad
   mov ebp, esp
   mov eax, [ebp+4*9]		       ; call的返回地址占4字节+pushad的8个4字节
   mov edx, eax
   mov edi, 7                          ; 指定在put_int_buffer中初始的偏移量
   mov ecx, 8			       ; 32位数字中,16进制数字的位数是8个
   mov ebx, put_int_buffer

;将32位数字按照16进制的形式从低位到高位逐个处理,共处理8个16进制数字
.16based_4bits:			       ; 每4位二进制是16进制数字的1位,遍历每一位16进制数字
   and edx, 0x0000000F		       ; 解析16进制数字的每一位。and与操作后,edx只有低4位有效
   cmp edx, 9			       ; 数字0～9和a~f需要分别处理成对应的字符
   jg .is_A2F 
   add edx, '0'			       ; ascii码是8位大小。add求和操作后,edx低8位有效。
   jmp .store
.is_A2F:
   sub edx, 10			       ; A~F 减去10 所得到的差,再加上字符A的ascii码,便是A~F对应的ascii码
   add edx, 'A'

;将每一位数字转换成对应的字符后,按照类似“大端”的顺序存储到缓冲区put_int_buffer
;高位字符放在低地址,低位字符要放在高地址,这样和大端字节序类似,只不过咱们这里是字符序.
.store:
; 此时dl中应该是数字对应的字符的ascii码
   mov [ebx+edi], dl		       
   dec edi
   shr eax, 4
   mov edx, eax 
   loop .16based_4bits

;现在put_int_buffer中已全是字符,打印之前,
;把高位连续的字符去掉,比如把字符000123变成123
.ready_to_print:
   inc edi			       ; 此时edi退减为-1(0xffffffff),加1使其为0
.skip_prefix_0:  
   cmp edi,8			       ; 若已经比较第9个字符了，表示待打印的字符串为全0 
   je .full0 
;找出连续的0字符, edi做为非0的最高位字符的偏移
.go_on_skip:   
   mov cl, [put_int_buffer+edi]
   inc edi
   cmp cl, '0' 
   je .skip_prefix_0		       ; 继续判断下一位字符是否为字符0(不是数字0)
   dec edi			       ;edi在上面的inc操作中指向了下一个字符,若当前字符不为'0',要恢复edi指向当前字符		       
   jmp .put_each_num

.full0:
   mov cl,'0'			       ; 输入的数字为全0时，则只打印0
.put_each_num:
   push ecx			       ; 此时cl中为可打印的字符
   call put_char
   add esp, 4
   inc edi			       ; 使edi指向下一个字符
   mov cl, [put_int_buffer+edi]	       ; 获取下一个字符到cl寄存器
   cmp edi,8
   jl .put_each_num
   popad
   ret
